#nullable disable
namespace Crux.Core.MongoDb;
/// <summary>
/// MongoDbExpansion
/// </summary>
public static class MongoDbExpansion
{
    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entity"></param>
    public static void Add<T, TPrimary>(this IMongoCollection<T> collection, T entity)
    {
        Requires.NotNull(entity, nameof(entity));

        collection.InsertOne(entity);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entitys"></param>
    public static void AddRange<T, TPrimary>(this IMongoCollection<T> collection, IEnumerable<T> entitys)
    {
        Requires.NotNullOrEmpty(entitys, nameof(entitys));

        collection.InsertMany(entitys);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entity"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async static Task AddAsync<T, TPrimary>(
        this IMongoCollection<T> collection,
        T entity,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNull(entity, nameof(entity));

        await collection.InsertOneAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false);
    }
    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entitys"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async static Task AddRangeAsync<T, TPrimary>(
        this IMongoCollection<T> collection,
        IEnumerable<T> entitys,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNullOrEmpty(entitys, nameof(entitys));

        await collection.InsertManyAsync(entitys, cancellationToken: cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entity"></param>
    public static void Update<T, TPrimary>(
        this IMongoCollection<T> collection,
        T entity) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNull(entity, nameof(entity));

        collection.ReplaceOne(o => o.Id.Equals(entity.Id), entity);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entity"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async static Task UpdateAsync<T, TPrimary>(
        this IMongoCollection<T> collection,
        T entity,
        CancellationToken cancellationToken = default) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNull(entity, nameof(entity));

        await collection.ReplaceOneAsync(o => o.Id.Equals(entity.Id), entity, cancellationToken: cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entities"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    /// <exception cref="NullReferenceException"></exception>
    public async static Task UpdateRangeAsync<T, TPrimary>(
        this IMongoCollection<T> collection,
        IEnumerable<T> entities,
        CancellationToken cancellationToken = default) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            await collection.ReplaceOneAsync(o => o.Id.Equals(entity.Id), entity, cancellationToken: cancellationToken).ConfigureAwait(false);
        }
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entity"></param>
    public static void Remove<T, TPrimary>(
        this IMongoCollection<T> collection,
        T entity) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNull(entity, nameof(entity));

        collection.DeleteOne(o => o.Id.Equals(entity.Id));
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entities"></param>
    public static void RemoveRange<T, TPrimary>(
        this IMongoCollection<T> collection,
        IEnumerable<T> entities) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            collection.DeleteOne(o => o.Id.Equals(entity.Id));
        }
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entities"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async static Task RemoveRangeAsync<T, TPrimary>(
        this IMongoCollection<T> collection,
        IEnumerable<T> entities,
        CancellationToken cancellationToken = default) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNullOrEmpty(entities, nameof(entities));

        foreach (var entity in entities)
        {
            await collection.DeleteOneAsync(o => o.Id.Equals(entity.Id), cancellationToken: cancellationToken).ConfigureAwait(false);
        }
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="expression"></param>
    public static void Remove<T, TPrimary>(
        this IMongoCollection<T> collection,
        Expression<Func<T, bool>> expression) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNull(expression, nameof(expression));

        collection.DeleteMany(expression);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="entity"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async static Task RemoveAsync<T, TPrimary>(
        this IMongoCollection<T> collection,
        T entity,
        CancellationToken cancellationToken = default) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNull(entity, nameof(entity));

        await collection.DeleteOneAsync(o => o.Id.Equals(entity.Id), cancellationToken: cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TPrimary"></typeparam>
    /// <param name="collection"></param>
    /// <param name="expression"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async static Task RemoveAsync<T, TPrimary>(
        this IMongoCollection<T> collection,
        Expression<Func<T, bool>> expression,
        CancellationToken cancellationToken = default) where T : IGenericEntity<TPrimary>
    {
        Requires.NotNull(expression, nameof(expression));

        await collection.DeleteManyAsync(expression, cancellationToken: cancellationToken);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="collection"></param>
    /// <param name="expression"></param>
    /// <returns></returns>
    public static IFindFluent<T, T> Where<T>(this IMongoCollection<T> collection, Expression<Func<T, bool>> expression)
    {
        Requires.NotNull(expression, nameof(expression));

        return collection.Find(expression);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="TDocument"></typeparam>
    /// <typeparam name="TProjection"></typeparam>
    /// <typeparam name="TNewProjection"></typeparam>
    /// <param name="IQueryable"></param>
    /// <param name="projection"></param>
    /// <returns></returns>
    public static IFindFluent<TDocument, TNewProjection> Select<TDocument, TProjection, TNewProjection>(
        this IFindFluent<TDocument, TProjection> IQueryable,
        Expression<Func<TDocument, TNewProjection>> projection)
    {
        Requires.NotNull(projection, nameof(projection));

        return IQueryable.Project(projection);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="TDocument"></typeparam>
    /// <typeparam name="T"></typeparam>
    /// <param name="IQueryable"></param>
    /// <returns></returns>
    public static T FirstOrDefault<TDocument, T>(this IFindFluent<TDocument, T> IQueryable)
    {
        return IFindFluentExtensions.FirstOrDefault(IQueryable);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="TDocument"></typeparam>
    /// <typeparam name="T"></typeparam>
    /// <param name="IQueryable"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public static async Task<T> FirstOrDefaultAsync<TDocument, T>(
        this IFindFluent<TDocument, T> IQueryable,
        CancellationToken cancellationToken = default)
    {
        return await IFindFluentExtensions.FirstOrDefaultAsync(IQueryable, cancellationToken).ConfigureAwait(false);
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="TDocument"></typeparam>
    /// <typeparam name="T"></typeparam>
    /// <param name="collection"></param>
    /// <param name="expression"></param>
    /// <returns></returns>
    public static T FirstOrDefault<TDocument, T>(
        this IMongoCollection<T> collection,
        Expression<Func<T, bool>> expression = null)
    {
        return expression is null ? collection.Find(Builders<T>.Filter.Empty).FirstOrDefault() : collection.Find(expression).FirstOrDefault(); ;
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="TDocument"></typeparam>
    /// <typeparam name="T"></typeparam>
    /// <param name="collection"></param>
    /// <param name="expression"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public static async Task<T> FirstOrDefaultAsync<TDocument, T>(
        this IMongoCollection<T> collection,
        Expression<Func<T, bool>> expression = null,
        CancellationToken cancellationToken = default)
    {
        return await (expression is null ? collection.Find(Builders<T>.Filter.Empty).FirstOrDefaultAsync(cancellationToken) : collection.Find(expression).FirstOrDefaultAsync(cancellationToken)).ConfigureAwait(false);
    }
}